Conversation
React Fiber will check this flag before attempting to inject itself. This avoids crashes on pre-Fiber DevTools version when Fiber is on the page.
| const current = root.current; | ||
| const alternate = current.alternate; | ||
| const events = []; | ||
| if (alternate && subscription != null) { |
There was a problem hiding this comment.
What if you're attaching to an existing tree that already has an alternate?
There was a problem hiding this comment.
Then subscription would be null (since we haven't finished subscribing, and the React side calls the commit hook synchronously during subscription the first time).
There was a problem hiding this comment.
When React calls it the first time (synchronously), then there can already be an alternate though, right?
There was a problem hiding this comment.
Right. But it's && so we would go into the other branch which always mounts.
| const prevChild = prevChildren.get(key); | ||
| const nextChild = nextChildren.get(key); | ||
| if (prevChild != null && nextChild != null) { | ||
| // TODO: are there more cases when we can bail out? |
There was a problem hiding this comment.
Currently, either all children are cloned or none are cloned. So you really only need to check nextFiber.child === prevFiber.child to know if all are the same or all are different.
There was a problem hiding this comment.
Is this an implementation detail that will hold true? I thought we might change this in the future as optimization but wasn't sure.
| } | ||
| } | ||
|
|
||
| function enqueueUpdate(fiber, events) { |
There was a problem hiding this comment.
If props, state and the others have reference equality, you shouldn't need to push any events here. Helpful for deep updates since nothing along the path will have been updated.
There was a problem hiding this comment.
I'd still need to compare children though, don't I? So that would be reference equality for everything except child ID array (which I'd compare shallowly).
| function updateFiber(nextFiber, prevFiber, events) { | ||
| // TODO: optimize for the common case of children with implcit keys. | ||
| // Just like we do in the Fiber child reconciler. | ||
| // We shouldn't be allocating Maps all the time. |
There was a problem hiding this comment.
You shouldn't even need to diff on keys and types (which is incomplete anyway) since you can match on object equality between the fiber alternates.
If the first child is equal, then all the children are equal. If the first child is non-equal, you know that you will need to enqueue and update on everything that has an alternate and a mount on anything without an alternate.
The only thing you might need diffing for is unmounts. One solution is to just loop through all the next children with alternates. Add all the alternates to a Set. Then loop through all the previous children, if they're not in the Set, they're deleted.
A better solution might be to just expose a hook for deletions instead.
There was a problem hiding this comment.
Nice, thanks for explaining!
There was a problem hiding this comment.
You shouldn't even need to diff on keys and types (which is incomplete anyway)
Why is it incomplete though? What did I miss?
| }; | ||
| } | ||
|
|
||
| module.exports = getFiberData; |
There was a problem hiding this comment.
nit: File name is getDataFiber; exports function getFiberData. The latter sounds less awkward.
There was a problem hiding this comment.
same thing with attachFiberRenderer.
There was a problem hiding this comment.
We have existing getData013, getData012. I was striving for them all to appear together with their non-Fiber equivalents. But I don't care, can change for readability too.
There was a problem hiding this comment.
(Didn't notice the inconsistency—probably because I changed it some point. Which way do you prefer?)
There was a problem hiding this comment.
I'd maybe vote for consistency w/r/t getData12? so getDataFiber
|
Ready for another review. |
bvaughn
left a comment
There was a problem hiding this comment.
Some thoughts! 😄 Probably should be reviewed by at least one more person though.
| element: getOpaqueNode(fiber), | ||
| data: getDataFiber(fiber, getOpaqueNode), | ||
| renderer: rid, | ||
| _event: 'mount', |
There was a problem hiding this comment.
nit: Why not type or _type (eg event.type seems more intuitive than event._event)
There was a problem hiding this comment.
Mostly I wasn't sure if any downstream code handling these events gives any meaning to event.type or other property names. I'll check and rename. 😄
| } | ||
| opaqueNodes.delete(fiber); | ||
| if (fiber.alternate != null) { | ||
| opaqueNodes.delete(fiber.alternate); |
There was a problem hiding this comment.
nit: If you stored the value value returned on line 127 you would only have to delete that (and could avoid checking fiber.alternate again).
| let node = fiber; | ||
| outer: while (true) { | ||
| if (node.child) { | ||
| node.child.return = node; |
There was a problem hiding this comment.
Why does this function modify return pointers? That doesn't seem right. Am I reading this wrong? (Also when would node.child.return ever not equal node?)
There was a problem hiding this comment.
There was a problem hiding this comment.
They happen if return points to alternate of the parent you're traversing.
Wouldn't this only happen if there was a bug in fiber?
I expect these return assignments in devtools are basically neutral since the pointers should be correct to begin with, but it still seems weird to see something outside of React setting values on fibers.
There was a problem hiding this comment.
Wouldn't this only happen if there was a bug in fiber?
I don't know if Fiber guarantees that returns will match up anywhere. Fiber itself always reassigns them when traversing so that's what I did. I understand it seems a bit fishy but I don't see any harm in this.
There should be no observable side effects to reassigning return I think. However, if return doesn't match, we risk getting an infinite loop.
There was a problem hiding this comment.
It wouldn't be too bad to use recursion here instead if you wanted to avoid the concern and the implementation details of return. But then again, maybe this could should live colocated with the React Fiber source code so it feels less smelly.
There was a problem hiding this comment.
Meh, we rely on implementation details a lot anyway. I think it's fine if you don't mind. I don't want to further bloat the bundle since we decided to enable this in prod.
| _event: 'mount', | ||
| }); | ||
|
|
||
| const isRoot = fiber.tag === 3; |
There was a problem hiding this comment.
nit: Because of references like this and the ones in getDataFiber I wonder if it wouldn't be clearer to define a duplicate ReactTypeOfWork const in this project. At least this way if a value ever changes it's clear where all of the mirrored values are (and we aren't just greping for numbers).
| context = null; | ||
| } | ||
| } | ||
| const inst = publicInstance; |
There was a problem hiding this comment.
Why is the inst var needed?
There was a problem hiding this comment.
To avoid next lines breaking over 80 character lint :-)
I can restructure.
| } | ||
| }, | ||
| // Fiber-only: | ||
| supportsFiber: true, |
There was a problem hiding this comment.
No:
if (
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.supportsFiber
) {Necessary so that Fiber doesn't try to attach to non-Fiber DevTools and crash them.
There was a problem hiding this comment.
If we release a new version of the devtools that auto-updates everyone then before we release Fiber we can revert this check with the assumption that devtools gets updated before renderers?
| } | ||
| }, | ||
| onCommitFiberRoot: function(rendererID, root) { | ||
| const mountedRoots = this.getFiberRoots(rendererID); |
There was a problem hiding this comment.
It's unfortunate that the use of this instead of closure capture invalidates the suggestion I had in https://github.com/facebook/react/pull/8818/files#r98101132
| const mountedRoots = this.getFiberRoots(rendererID); | ||
| const current = root.current; | ||
| const isKnownRoot = mountedRoots.has(root); | ||
| const isUnmounting = current.memoizedState == null || current.memoizedState.element == null; |
There was a problem hiding this comment.
cc @acdlite This is regard to roots but I don't really have a better idea.
We could possibly make onCommitFiberRoot accept the direct children of roots instead of the actual root but it's nice to be able to have the mount container target here too for various debug data.
| const prevData = getDataFiber(fiber.alternate, getOpaqueNode); | ||
| // Avoid unnecessary updates since they are common | ||
| // when something changes deep in the tree due to setState. | ||
| if (!hasDataChanged(prevData, nextData)) { |
There was a problem hiding this comment.
Would it be better to do this at the fiber level instead? Maybe it's more fragile to updates but faster since you don't have to create the other objects.
sebmarkbage
left a comment
There was a problem hiding this comment.
There's a bunch of nits but I think conceptually this is a good start to land.
| prevData.ref !== nextData.ref || | ||
| prevData.source !== nextData.source || | ||
| prevData.props !== nextData.props || | ||
| prevData.state !== nextData.state || |
There was a problem hiding this comment.
During a forceUpdate I don't think state object reference has necessarily changed and yet the component can have been mutated deep below.
There was a problem hiding this comment.
cc @acdlite
So, we need to know this information for life-cycles too. Currently we schedule an update effect tag that will be consumed during the commit. You might be able to use that. Ideally we would only do that if there exists a componentDidUpdate life-cycle but maybe we can keep doing that for this purpose too.
There was a problem hiding this comment.
In 09517f2, I check .updateQueue.hasForcedUpdate on the committed work and it seems to be fine. Can I rely on this?
Instead of diffing the data, we diff the fibers themselves. We also keep track of child order changes so that we don't have to diff children separately. This lets us bail out more efficiently.
It is necessary in cases we get a different fiber than the one we started with.
* Initial support for Fiber * Set a flag indicating Fiber-capable DevTools React Fiber will check this flag before attempting to inject itself. This avoids crashes on pre-Fiber DevTools version when Fiber is on the page. * Rebuild * Specify the missing key * Make function naming consistent * Attach to the tree lazily * Implement DOM selection * Implement updater for composites * Avoid diffing children * Support portals * Rebuild * Fix lint and flow * The bridge is async so we may receive unmounted fibers * Fix comment * Compare fibers more efficiently Instead of diffing the data, we diff the fibers themselves. We also keep track of child order changes so that we don't have to diff children separately. This lets us bail out more efficiently. * _event => type * Simplify unmounting code * Copy ReactTypeOfWork enum * Address feedback * Add opaque node lookup to getReactElementFromNative() It is necessary in cases we get a different fiber than the one we started with. * Fix Flow
This works on top of facebook/react#8818.
We diff trees on every commit and create a list of events to send to the hook.